﻿using Jering.Javascript.NodeJS;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Net;
using System.Threading;
using System.Threading.Tasks;

namespace FieldTools.Util
{
    /// <summary>
    /// JS解释器
    /// 源自开源项目：https://github.com/JeringTech/Javascript.NodeJS，如有额外需求，可根据官方文档自行进行扩展
    /// </summary>
    public class JSHelper
    {
        /// <summary>
        /// 运行js文件，需使用require来引用模块，import不行
        /// </summary>
        /// <param name="modulePath">本地模块文件地址</param>
        /// <param name="exportName">调用名</param>
        /// <param name="args">传入参数集合</param>
        /// <param name="cancellationToken">异步操作的取消令牌</param>
        /// <returns></returns>
        public static async Task<string?> RunJsFromFile(string modulePath, string? exportName = null, object?[]? args = null, CancellationToken cancellationToken = default)
        {
            Result? result = null;
            try
            {
                var services = new ServiceCollection();
                services.AddNodeJS();

                // Options for the NodeJS process, here we enable debugging
                services.Configure<NodeJSProcessOptions>(options => options.NodeAndV8Options = "--inspect-brk");

                services.Configure<OutOfProcessNodeJSServiceOptions>(options => options.ConnectionTimeoutMS = -1);
                services.Configure<HttpNodeJSServiceOptions>(options => options.Version = HttpVersion.Version20);

                ServiceProvider serviceProvider = services.BuildServiceProvider();
                INodeJSService nodeJSService = serviceProvider.GetRequiredService<INodeJSService>(); // Configured INodeJSService
                result = await nodeJSService.InvokeFromFileAsync<Result>(modulePath, exportName, args, cancellationToken);
            }
            catch (Exception ex)
            {
                return ex.Message;
            }
            return result?.ResJson;
        }
        /// <summary>
        /// 运行js字符串，由于无文件位置，无法直接引用模块
        /// </summary>
        /// <param name="moduleString">js字符串</param>
        /// <param name="cacheIdentifier">缓存名，定义缓存名后，使用缓存名调用可不再重新编辑，提高运行效率</param>
        /// <param name="exportName">调用名</param>
        /// <param name="args">传入参数集合</param>
        /// <param name="cancellationToken">异步操作的取消令牌</param>
        /// <returns></returns>
        public static async Task<string?> RunJsFromStr(string moduleString, string? cacheIdentifier = null, string? exportName = null, object?[]? args = null, CancellationToken cancellationToken = default)
        {
            Result? result = null;
            try
            {
                var services = new ServiceCollection();
                services.AddNodeJS();

                // Options for the NodeJS process, here we enable debugging
                services.Configure<NodeJSProcessOptions>(options => options.NodeAndV8Options = "--inspect-brk");

                // Options for the INodeJSService implementation
                // - HttpNodeJSService is the default INodeJSService implementation. It communicates with the NodeJS process via HTTP. Below, we set the HTTP version it uses to HTTP/2.0.
                // - HttpNodeJSService extends OutOfProcessNodeJSService, an abstraction for NodeJS process management. Below we set the timeout for connecting to the NodeJS process and for invocations to -1 (infinite).
                services.Configure<OutOfProcessNodeJSServiceOptions>(options => options.ConnectionTimeoutMS = -1);
                services.Configure<HttpNodeJSServiceOptions>(options => options.Version = HttpVersion.Version20);

                ServiceProvider serviceProvider = services.BuildServiceProvider();
                INodeJSService nodeJSService = serviceProvider.GetRequiredService<INodeJSService>(); // Configured INodeJSService
                if (cacheIdentifier == null)
                {
                    result = await nodeJSService.InvokeFromStringAsync<Result>(moduleString, cacheIdentifier, exportName, args, cancellationToken);
                }
                else
                {
                    //缓存js字符串，重复调用时候无需重新编译，节省资源与时间

                    // Try to invoke from the NodeJS cache
                    (bool success, result) = await nodeJSService.TryInvokeFromCacheAsync<Result>(cacheIdentifier, exportName, args: args, cancellationToken);

                    // If the module hasn't been cached, cache it. If the NodeJS process dies and restarts, the cache will be invalidated, so always check whether success is false.
                    if (!success)
                    {
                        // This is a trivialized example. In practice, to avoid holding large module strings in memory, you might retrieve the module 
                        // string from an on-disk or remote source.

                        // Send the module string to NodeJS where it's compiled, invoked and cached.
                        result = await nodeJSService.InvokeFromStringAsync<Result>(moduleString, cacheIdentifier, exportName, args: args, cancellationToken);
                    }
                }
            }
            catch (Exception ex)
            {
                return ex.Message;
            }
            return result?.ResJson;
        }
    }
    /// <summary>
    /// 返回数据
    /// </summary>
    public class Result
    {
        /// <summary>
        /// 返回数据JSON
        /// </summary>
        public string? ResJson { get; set; }
    }
}